package samples;

import its.model.environment.Environment;
import its.model.environment.crossroad.Crossroad;
import its.model.environment.crossroad.Program;
import its.opt.OptSimConf;
import its.opt.StatsLoader;
import its.opt.program.Evaluator;
import its.opt.program.ProgramBuilder;
import its.opt.structures.EvolutionParams;
import its.opt.utils.EvaluationFunctionFactory;
import its.opt.utils.LoggingUtils;
import its.opt.utils.OptimizationEvaluatorUtils;
import its.sim.Simulator;
import its.sim.SimulatorConfiguration;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import samples.objectivefunctions.ProgramTrafficAmount;
import engine.Algorithm;
import engine.CachedObjectiveFunction;
import engine.Population;
import engine.PopulationEvaluator;
import engine.SingleThreadedEvaluator;
import engine.exitcriteria.AllIndividualsAreSimilar;
import engine.exitcriteria.MaxIterations;
import engine.exitcriteria.NoImprovementTerminationCondition;
import engine.individuals.NaturalTrafficNetIndividual;
import engine.operators.ChartReportingMinAvgMaxOperator;
import engine.operators.CombineOperators;
import engine.operators.CombineOperatorsWithEvaluation;
import engine.operators.ReportStatisticsOperator;
import engine.operators.RouletteReproduction;
import engine.operators.WorstHalfCutoff;
import engine.operators.natural.NaturalConstrainedMutation;
import engine.operators.natural.NaturalMeanCrossover;
import engine.utils.ListUtils;

public class NaturalItsSga {

	private static final Logger LOGGER = Logger.getLogger(NaturalItsSga.class
			.getName());

	private int MAX_ITERATIONS  = 100;
	private int POPULATION_SIZE = 50;

	private SimulatorConfiguration conf;

	@SuppressWarnings("unchecked")
	public void run(String[] args) {

		conf = new SimulatorConfiguration(args);

		if (LOGGER.isLoggable(Level.INFO)) {
			LOGGER.info(LoggingUtils.makeLogStringForAlgorithmParameters(args, conf));
		}

		StatsLoader init_loader = null;
		try {
			init_loader = runSimulation();
		} catch (Exception e) {
			LOGGER.severe("Error during simulation run\n" + e);
			throw new IllegalStateException(e);
		}

		// SimulatorFiles simulationFiles =
		// new SimulatorFiles(new SimulatorConfiguration(args).getConfigFile());

		Evaluator evaluator = EvaluationFunctionFactory.getEvaluator(args, conf,
				init_loader);

		EvolutionParams evoParams = prepareParams();
		Population<NaturalTrafficNetIndividual> population = initializePopulation(evoParams);
		
		if (LOGGER.isLoggable(Level.INFO)) {
			LOGGER.info(LoggingUtils.makeLogStringForAlgorithmParameters(evoParams));
		}

		Algorithm<NaturalTrafficNetIndividual> alg = new Algorithm<NaturalTrafficNetIndividual>(
				population);

		ProgramTrafficAmount programTrafficAmount = new ProgramTrafficAmount(
				evaluator);

		CachedObjectiveFunction<NaturalTrafficNetIndividual> objectiveFunctionWrapper = new CachedObjectiveFunction<NaturalTrafficNetIndividual>(
				programTrafficAmount);

		ChartReportingMinAvgMaxOperator<NaturalTrafficNetIndividual> chartReportingOperator = new ChartReportingMinAvgMaxOperator<NaturalTrafficNetIndividual>(
				"natural-sga", conf.getConfigFile().toString(),
				objectiveFunctionWrapper);

		alg.addExitPoint(new MaxIterations<NaturalTrafficNetIndividual>(
				evoParams.maxGenerations));
		alg
				.addExitPoint(new AllIndividualsAreSimilar<NaturalTrafficNetIndividual>(objectiveFunctionWrapper));
		alg
				.addExitPoint(new NoImprovementTerminationCondition<NaturalTrafficNetIndividual>(
						objectiveFunctionWrapper));

		PopulationEvaluator<NaturalTrafficNetIndividual> populationEval = buildObjectiveFunctions(objectiveFunctionWrapper);
//		alg.addEvaluationPoint(populationEval);
		
		CombineOperators<NaturalTrafficNetIndividual> mainLogic =
			new CombineOperators<NaturalTrafficNetIndividual>(
					new RouletteReproduction<NaturalTrafficNetIndividual>(objectiveFunctionWrapper),
					new NaturalMeanCrossover<NaturalTrafficNetIndividual>(),
					new NaturalConstrainedMutation<NaturalTrafficNetIndividual>(evoParams.mutationProbability));
		
		alg.addOperator(new CombineOperatorsWithEvaluation<NaturalTrafficNetIndividual>(
				mainLogic,
				populationEval,
				new WorstHalfCutoff<NaturalTrafficNetIndividual>(objectiveFunctionWrapper)));
		
		ReportStatisticsOperator<NaturalTrafficNetIndividual> statReporter = new ReportStatisticsOperator<NaturalTrafficNetIndividual>(
				objectiveFunctionWrapper);
		
		alg.addOperator(statReporter);
		alg.addOperator(chartReportingOperator);

		populationEval.apply(population);
		statReporter.apply(population);

		alg.run();

		chartReportingOperator.drawStatistics();

		if (LOGGER.isLoggable(Level.INFO)) {
			LOGGER.info(alg.getPopulation().toString());
			LOGGER.info("Good rated individuals: "
					+ programTrafficAmount.getGoodIndividuals());
		}

		// save best individual
		OptimizationEvaluatorUtils.saveBestIndividual(args, alg,
				objectiveFunctionWrapper);
	}

	

	private EvolutionParams prepareParams() {
		EvolutionParams params = new EvolutionParams();

		String populationSize = System.getProperty("population.size");
		if (populationSize != null && populationSize.trim().length() > 0) {
			POPULATION_SIZE = Integer.parseInt(populationSize);
		}
		String maxGenerations = System.getProperty("max.generations");
		if (maxGenerations != null && maxGenerations.trim().length() > 0) {
			MAX_ITERATIONS = Integer.parseInt(maxGenerations);
		}
//		int percentOffspring = 50;
//		String percentOffspringStr = System.getProperty("percent.offspring");
//		if (percentOffspringStr != null && percentOffspringStr.trim().length() > 0) {
//			percentOffspring = Integer.parseInt(percentOffspringStr);
//		}
		double mutationProb = 0.3d;
		String mutationProbStr = System.getProperty("mutation.probability");
		if (mutationProbStr != null && mutationProbStr.trim().length() > 0) {
			mutationProb = Double.parseDouble(mutationProbStr);
		}
//		int tournamentSize = 7;
//		String tournamentSizeStr = System.getProperty("tournament.size");
//		if (tournamentSizeStr != null && tournamentSizeStr.trim().length() > 0) {
//			tournamentSize = Integer.parseInt(tournamentSizeStr);
//		}
		
		params.N = POPULATION_SIZE;
		// defined elsewhere
//		params.n = CHROMOSOME_SIZE;
		params.maxGenerations = MAX_ITERATIONS;
		// Unused in production mode
		// params.randSeed = 123456L;
		// unused
//		params.tournamentSize = tournamentSize;
		params.mutationProbability = mutationProb;

		return params;
	}

	private Population<NaturalTrafficNetIndividual> initializePopulation(
			EvolutionParams params) {

		List<NaturalTrafficNetIndividual> result = new ArrayList<NaturalTrafficNetIndividual>(
				params.N);

		Collection<Crossroad> crossRoads = Environment.getActiveEnvironment()
				.getAllCrossroads();

		for (int i = 0; i < params.N; i++) {
			List<Program> res = new ArrayList<Program>(crossRoads.size());
			for (Crossroad crossroad : crossRoads) {
				ProgramBuilder programBuilder = new ProgramBuilder(crossroad.getID());
				Program randomProgram = programBuilder
						.getRandomProgramFromSimplexEdge();
				
				res.add(randomProgram);
			}

			// we dont want to move that unnecessarily
			// since there is lot of other stuff to
			// prarametrize
			 res.get(res.size() - 1).setPhaseShift(0L);

			NaturalTrafficNetIndividual ind = new NaturalTrafficNetIndividual(res
					.toArray(new Program[res.size()]));
			result.add(ind);

		}

		params.n = result.get(0).size();

		return new Population<NaturalTrafficNetIndividual>(result);
	}

	@SuppressWarnings("unchecked")
	private PopulationEvaluator<NaturalTrafficNetIndividual> buildObjectiveFunctions(
			CachedObjectiveFunction<NaturalTrafficNetIndividual> objectiveFunctionWrapper) {
		return new SingleThreadedEvaluator<NaturalTrafficNetIndividual>(ListUtils
				.buildList(objectiveFunctionWrapper));
	}

	private StatsLoader runSimulation() throws Exception {
		final SimulatorConfiguration opt_conf = new OptSimConf(conf);

		final Simulator sim = new Simulator(opt_conf);
		LOGGER.info("Running simulation, please wait");
		sim.run();
		return new StatsLoader(sim);
	}

	public static void main(String[] args) {
		
		Date d1 = new Date();
		new NaturalItsSga().run(args);
		Date d2 = new Date();
		LOGGER.info(new Long((d2.getTime() - d1.getTime()) / 1000L).toString());
	}
}
